\chapter{为何 Qt 使用 Moc 实现信号槽？}

模板是 C++ 的内建机制，可以允许编译器基于传递的参数类型，在编译期生成代码。
因此，框架编写者很喜欢使用模板，而我们也的确在 Qt 的许多地方使用了高阶的模板特性。然而，模板有限制的：
有的东西可以用模板很轻易地表达，但同样也会有几乎无法用模板表达的东西。
一个通用的向量容器类很容易用模板表达，即使它在针对指针类型时使用到了偏特化特性；
然而一个基于 XML 字符串的内容描述来构建用户界面的函数，就无法用模板来表达。
并且，在它俩之间还有一块灰色区域。
强行使用模板来实现功能，会付出代码体积、可读性、可移植性、可用性、
可扩展性、健壮性乃至最根本的设计美感上的代价。
C++ 的模板和 C 的宏都可以用于扩展语法，来实现不可思议又难以置信的奇思妙想，
但这只代表这些想法是可行的，
而未必意味着这么做是正确的设计思路。很不幸，编写代码，并不是用来写进书籍（译者注：即阳春白雪般的纸上谈兵），
而是在真实世界的操作系统中，被真实世界的编译器所编译。

这就是为什么 Qt 使用 moc 的原因：

\section{语法很重要}

语法并不只是糖：用于描述我们的算法的语法，会显著地影响我们的代码的可读性和可维护性。
Qt 的信号槽语法被证明是非常成功的实践，它的语法非常直观，易于使用也易于阅读。
学习 Qt 时，这种语法可以帮助人们更好地理解和使用信号槽这个概念——尽管它本质上是很高阶的通用抽象。
这令开发者们在刚踏入门槛时，就能做出正确的设计，甚至都不需要去思考何为设计模式。

\section{代码生成是好东西}

Qt 的 moc(Meta Object Compiler，元对象编译器) 提供了一种简洁的方式，
来超越目标语言本身的桎梏。它通过生成额外的 C++ 代码来达成目标，而这些代码可以被任何标准的 C++ 编译器所编译。
moc 会阅读 C++ 源码文件，如果找到任何类声明中包含了 Q\_OBJECT 宏，
就会生成另一个 C++ 源码文件，在其中填充这些类的元对象相关的代码。
moc 生成的 C++ 源码文件必须与对应类的实现文件共同编译和链接（也可以被\#include包含到对应类的源文件中）。
但通常来说，moc 并不会被手动调用，而是被编译系统自动调用，因此不需要开发者做额外工作。

moc 并不是 Qt 使用的唯一一个代码生成器。
另一个典型范例则是 uic(User Interface Compiler，用户界面编译器)，
它接受 XML 格式的用户界面描述，来生成 C++ 代码并初始化界面。在 Qt 之外，
代码生成器同样也被广泛应用，例如 rpc 和 idl，
可以支撑应用程序或对象跨越进程甚至跨机器进行通信。又比如，
各种各样的词法分析工具，如大名鼎鼎的 lex 和 yacc，
它们将语法规范作为输入，来生成可以实现这些规范的状态机代码。
代码生成器的其它替代品有改造编译器、专有语言或者可视化编程工具——后者提供单向的对话框/向导，
用于在设计阶段而非编译阶段生成各种晦涩的代码。相比于将我们的客户绑定在专用的 C++ 编译器上，
或者绑定到特定的集成开发环境(IDE)中，我们允许他们使用任何他们喜欢的工具。
相较于强迫开发者将生成的代码添加到代码库中，
我们更鼓励他们将我们的工具添加到他们的构建系统中：更加干净、更加安全、更具有 UNIX 精神。

\begin{quote}
译者注：UNIX 精神指的是用小型单一工具组合完成任务，而非庞大的、无所不能的复杂工具。
\end{quote}


\section{用户界面是动态的}

C++ 是一门标准化的、强大的、经过精心设计的通用型语言，它是唯一一门被应用于如此广泛的软件开发领域的语言，囊括了所有种类的应用程序，
包括完整的操作系统、数据库服务器、高级图形应用乃至于通用桌面应用。C++ 成功的关键之一，便是在提供可伸缩的语言特性的同时，聚焦于最大化性能和最小化内存占用，同时还保持了对 ANSI C 的兼容性。
除此之外，C++ 也有一些缺点。例如在面对基于组件的用户界面开发时，C++ 的静态对象模型相较于 Objective-C 的动态消息机制便是很显著的劣势。
对于高级数据库服务器或者操作系统而言的优秀特性，却并非用户界面前端设计的正确选择。
拥有了 moc 后，我们可以将这个劣势转化为优势，为满足安全又高效的用户界面开发提供了充足的灵活性。
我们的成果已经超越了通过模板能够做到的任何事情。例如，我们可以拥有对象属性，
还可以重载信号槽，这在一门把重载作为关键特性的语言中，会令开发者感到无比自然。我们的信号机制不会让对象大小增加任何一个字节，这意味着我们在添加新的信号的同时不会破坏二进制兼容性。
另一个好处是我们可以在运行时检索一个对象的信号和槽。我们可以通过名称（译者注：字符串）来做到类型安全地建立连接，
而并不需要知道我们连接的对象使用的具体类型（译者注：包括对象类型和参数类型），这对于基于模板的解决方案来说是不可能的。这种运行时的自省机制为我们开启了新的可能，例如，可以通过 Qt Designer 的 XML 界面文件来生成图形界面，并完成信号槽的连接。

\section{调用性能并不代表一切}

Qt 的信号槽实现并不能和基于模板的解决方案一样快。
尽管发射一个信号大约只有四次常规模板函数的调用开销，
整个信号槽执行的过程也只被 Qt 尽量压缩到10倍函数调用开销。
这并不令人意外，因为 Qt 的机制包括了通用的序列化、自省、不同线程间的队列执行以及极致的脚本化。
它并不需要激进的内联和代码展开，但却提供了远超于这些代价的运行时安全性。
Qt 的信号分发机制是安全的，而那些速度更快的基于模板的系统则不是。
即使在发送一个信号至多个不同的接收者的过程中，
您依然可以安全地删除这些接收者，而不会引发程序崩溃。
如果没有这份安全性，您的程序可能会偶发性地崩溃，
并伴随一个痛苦的调试过程，来修复错误地对已经被释放掉的内存进行的读写操作。


\begin{quote}
译者注：

本段翻译有待商榷，其中一句

Qt 的信号分发机制是安全的，而那些速度更快的基于模板的系统则不是。

对应的原文是

Qt's iterators are safe while those of faster template-based systems are not.

从字面理解，该句是指 Qt 容器的迭代器。然而结合上下文，该句应该也是用于描述信号槽机制的安全性，所以此处理解为信号分发过程中，遍历分发目标的安全性，即 iteration 操作。

然而，从 Qt 容器安全性上理解也未尝不可。虽然迭代器算法是和容器数据结构强相关，对于相同类型的容器，Qt 迭代器与 STL 迭代器算法本质上并无区别，但 STL 容器是由标准库实现，而不同平台的标准库实现是针对该平台高度特化的，这导致了即使是很规范的 C++ 代码，移植到不同平台后依然可能因为平台差异（如大小端、位宽、对齐）产生 bug——而这不会出现在 Qt 中，因为 Qt 的设计是宁可舍弃平台特化的性能，也要保证兼容性。
\end{quote}


尽管如此，难道就不能用基于模板的方案来提升使用信号槽的应用的性能吗？
尽管 Qt 的确在信号槽调用中增加了一点点额外开销，这个开销对于槽的整个执行过程只占了很小的比例。
只要在槽里做了任何有效的操作，例如一些简单的字符串处理，那么调用时的额外开销就可以忽略不计了。
Qt 的系统已经经过充分优化，以至于任何需要new/delete的操作（例如字符串操作或向模板容器中插入/删除对象），
都比发射一次信号有更可观的开销。

例外：如果您在某个性能敏感任务的内部循环中使用了信号槽，并且确认它成为性能瓶颈，
那么可以考虑将其更换为监听者模式（译者注：更通用的称呼是发布-订阅或生产者-消费者）。
在这类场景中，您可能只需要一对一的连接方式。例如，如果有一个对象从网络中下载数据，
使用信号来标识需要的数据已经接收到，会是明智的设计；
但如果需要将每个字节逐一发送至一个消费者，就应该使用监听者模式而非信号槽。

\section{不受限制}

因为有 moc 来实现信号槽，我们可以添加更多模板所不能做到的东西。
其中便有带作用域的翻译器，可以通过生成的 tr() 函数使用；
还有一个先进的属性系统，具备运行时的自省和类型信息。
属性系统具有一个显著的优点：如 Qt Disigner 这类强大而且通用的可视化界面设计工具，
如果没有强大的支持反射的属性系统支撑的话，
会极其难以编写（如果的确能够编写出来的话）。
但不止于此，我们还提供了一个 qobject\_cast<T>() 动态转换机制，
并且不需要依赖系统的 RTTI 特性，也不需要承担 RTTI 所受的限制，
我们使用它来从动态加载的组件中安全地获取接口。
另一个应用领域是动态的元对象，例如，我们可以获取一个 ActiveX 组件，
并且在运行时为其生成对应的元对象；或者，我们也可以通过导出元对象的形式，将 Qt 组件导出为 ActiveX 组件。使用模板无法做到其中任意一个。

C++ 结合 moc 为我们提供了类似于 Objective-C 或者 JRE(Java Runtime Environment, 
Java 运行时环境) 的灵活性，
但同时依然保留了 C++ 独有的性能与扩展性优势。这让我们如今使用的 Qt 成为了一个灵活而方便的工具。